﻿using System;
using System.Collections.Generic;
using System.Text;

namespace FileTransfer.BlockCipher
{
    public enum Algorithm
    {
        None = 0,
        GOST = 1,
        Blowfish = 2
    }

    public abstract class BlockCipher
    {
        protected struct Block
        {
            public uint L, R;
            public Block(uint left, uint right)
            {
                L = left;
                R = right;
            }
            public Block(byte[] bytesOf8)
            {
                if (bytesOf8.Length != 8) throw new ArgumentException("The initializer array should be of 8 bytes size");

                L = (((uint)bytesOf8[0]) << 24) + (((uint)bytesOf8[1]) << 16) + (((uint)bytesOf8[2]) << 8) + ((uint)bytesOf8[3]);
                R = (((uint)bytesOf8[4]) << 24) + (((uint)bytesOf8[5]) << 16) + (((uint)bytesOf8[6]) << 8) + ((uint)bytesOf8[7]);
            }

            public byte[] GetBytes()
            {
                byte[] bytes = new byte[8];

                bytes[0] = (byte)(L >> 24);
                bytes[1] = (byte)((L << 8) >> 24);
                bytes[2] = (byte)((L << 16) >> 24);
                bytes[3] = (byte)((L << 24) >> 24);

                bytes[4] = (byte)(R >> 24);
                bytes[5] = (byte)((R << 8) >> 24);
                bytes[6] = (byte)((R << 16) >> 24);
                bytes[7] = (byte)((R << 24) >> 24);


                return bytes;
            }

            public void Swap()
            {
                uint temp = R;
                R = L;
                L = temp;
            }
            public static Block operator ^(Block x, Block y)
            {
                return new Block(y.L ^ x.L, y.R ^ x.R);
            }
            public static bool operator ==(Block self, Block other)
            {
                return (self.L == other.L) && (self.R == other.R);
            }
            public static bool operator !=(Block self, Block other)
            {
                return (self.L != other.L) || (self.R != other.R);
            }
            public override bool Equals(object obj)
            {
                return this == (Block)obj;
            }
            public override int GetHashCode()
            {
                return base.GetHashCode();
            }
        }

        protected enum CipherDirection
        {
            ENCIPHER, DECIPHER
        }


        protected uint keyLength;
        protected uint[] key;
        public BlockCipher(byte[] key)
        {
            if (key.Length % 4 != 0) throw new ArgumentException("The number of bytes in key chain must be divisible by four.");
            keyLength = (uint)key.Length / 4; // 4 bytes in uint
            this.key = new uint[keyLength];

            for (int i = 0; i < keyLength * 4; i += 4)
            {
                this.key[i / 4] = (((uint)key[i]) << 24) + (((uint)key[i + 1]) << 16) + (((uint)key[i + 2]) << 8) + ((uint)key[i + 3]);
            }
        }
        public byte[] Encipher(byte[] plain, out byte padded)
        {
            List<byte> plainList = new List<byte>(plain);

            padded = (byte)((8 - (byte)(plainList.Count % 8)) % 8);

            for (byte i = 0; i < padded; ++i)
            {
                plainList.Add(0);
            }


            byte[] paddedBytes = plainList.ToArray();

            List<Block> plainBlocks = new List<Block>();

            for (int i = 0; i < plainList.Count; i += 8)
            {
                byte[] sub = new byte[8];
                Array.Copy(paddedBytes, i, sub, 0, 8);
                plainBlocks.Add(new Block(sub));
            }

            List<Block> encBlocks = Encipher(plainBlocks);

            List<byte> encBytes = new List<byte>();
            foreach (var item in encBlocks)
            {
                encBytes.AddRange(item.GetBytes());
            }


            return encBytes.ToArray();
        }
        public byte[] Decipher(byte[] cipher, byte padded)
        {
            if (padded >= 8) throw new ArgumentOutOfRangeException("The number of padded bytes should be between 0 and 7");
            if (cipher.Length % 8 != 0) throw new ArgumentException("The initializer array should be of 8 bytes size");

            List<Block> cipherBlocks = new List<Block>();

            for (int i = 0; i < cipher.Length; i += 8)
            {
                byte[] sub = new byte[8];
                Array.Copy(cipher, i, sub, 0, 8);
                cipherBlocks.Add(new Block(sub));
            }

            List<Block> plainBlocks = Decipher(cipherBlocks);

            List<byte> plainBytes = new List<byte>();
            foreach (var item in plainBlocks)
            {
                plainBytes.AddRange(item.GetBytes());
            }


            plainBytes.RemoveRange(plainBytes.Count - padded, padded);

            return plainBytes.ToArray();
        }

        protected virtual List<Block> Encipher(List<Block> plain)
        {
            var cipher = new List<Block>();

            foreach (var item in plain)
            {
                cipher.Add(EncipherBlock(item));
            }
            return cipher;
        }
        protected virtual List<Block> Decipher(List<Block> cipher)
        {
            var plain = new List<Block>();

            foreach (var item in cipher)
            {
                plain.Add(DecipherBlock(item));
            }
            return plain;
        }

        protected abstract Block EncipherBlock(Block item);

        protected abstract Block DecipherBlock(Block item);

    }
    public abstract class FeistelCipher : BlockCipher
    {
        public FeistelCipher(byte[] key)
            : base(key)
        {

        }
        protected abstract uint FeistelFunction(uint a, uint key);

        protected abstract Block FeistelPermutation(Block x, uint iter, CipherDirection dir);

    }
}
